<script lang="tsx">
import { computed, defineComponent, reactive, ref, unref, watchEffect } from 'vue'
import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
import resumeSvg from '@/assets/svg/preview/resume.svg'
import rotateSvg from '@/assets/svg/preview/p-rotate.svg'
import scaleSvg from '@/assets/svg/preview/scale.svg'
import unScaleSvg from '@/assets/svg/preview/unscale.svg'
import unRotateSvg from '@/assets/svg/preview/unrotate.svg'

enum StatueEnum {
  LOADING,
  DONE,
  FAIL,
}
interface ImgState {
  currentUrl: string
  imgScale: number
  imgRotate: number
  imgTop: number
  imgLeft: number
  currentIndex: number
  status: StatueEnum
  moveX: number
  moveY: number
  show: boolean
}
const props = {
  show: {
    type: Boolean as PropType<boolean>,
    default: false,
  },
  imageList: {
    type: Array as PropType<string[]>,
    default: null,
  },
  index: {
    type: Number as PropType<number>,
    default: 0,
  },
  scaleStep: {
    type: Number as PropType<number>,
  },
  defaultWidth: {
    type: Number as PropType<number>,
  },
  maskClosable: {
    type: Boolean as PropType<boolean>,
  },
  rememberState: {
    type: Boolean as PropType<boolean>,
  },
}

const prefixCls = 'img-preview'
export default defineComponent({
  name: 'ImagePreview',
  props,
  emits: ['imgLoad', 'imgError'],
  setup(props, { expose, emit }) {
    interface stateInfo {
      scale: number
      rotate: number
      top: number
      left: number
    }
    const stateMap = new Map<string, stateInfo>()
    const imgState = reactive<ImgState>({
      currentUrl: '',
      imgScale: 1,
      imgRotate: 0,
      imgTop: 0,
      imgLeft: 0,
      status: StatueEnum.LOADING,
      currentIndex: 0,
      moveX: 0,
      moveY: 0,
      show: props.show,
    })

    const wrapElRef = ref<HTMLDivElement | null>(null)
    const imgElRef = ref<HTMLImageElement | null>(null)

    // 初始化
    function init() {
      initMouseWheel()
      const { index, imageList } = props

      if (!imageList || !imageList.length)
        throw new Error('imageList is undefined')

      imgState.currentIndex = index
      handleIChangeImage(imageList[index])
    }

    // 重置
    function initState() {
      imgState.imgScale = 1
      imgState.imgRotate = 0
      imgState.imgTop = 0
      imgState.imgLeft = 0
    }

    // 初始化鼠标滚轮事件
    function initMouseWheel() {
      const wrapEl = unref(wrapElRef)
      if (!wrapEl)
        return

      ;(wrapEl as any).onmousewheel = scrollFunc
      // 火狐浏览器没有onmousewheel事件，用DOMMouseScroll代替
      document.body.addEventListener('DOMMouseScroll', scrollFunc)
      // 禁止火狐浏览器下拖拽图片的默认事件
      document.ondragstart = function () {
        return false
      }
    }

    const getScaleStep = computed(() => {
      const scaleStep = props?.scaleStep ?? 0
      if (scaleStep ?? (scaleStep < 100))
        return scaleStep / 100
      else
        return imgState.imgScale / 10
    })

    // 监听鼠标滚轮
    function scrollFunc(e: any) {
      e = e || window.event
      e.delta = e.wheelDelta || -e.detail

      e.preventDefault()
      if (e.delta > 0) {
        // 滑轮向上滚动
        scaleFunc(getScaleStep.value)
      }
      if (e.delta < 0) {
        // 滑轮向下滚动
        scaleFunc(-getScaleStep.value)
      }
    }
    // 缩放函数
    function scaleFunc(num: number) {
      // 最小缩放
      const MIN_SCALE = 0.02
      // 放大缩小的颗粒度
      const GRA = 0.1
      if (imgState.imgScale <= 0.2 && num < 0)
        return
      imgState.imgScale += num * GRA
      // scale 不能 < 0，否则图片会倒置放大
      if (imgState.imgScale < 0)
        imgState.imgScale = MIN_SCALE
    }

    // 旋转图片
    function rotateFunc(deg: number) {
      imgState.imgRotate += deg
    }

    // 鼠标事件
    function handleMouseUp() {
      const imgEl = unref(imgElRef)
      if (!imgEl)
        return
      imgEl.onmousemove = null
    }

    // 更换图片
    function handleIChangeImage(url: string) {
      imgState.status = StatueEnum.LOADING
      const img = new Image()
      img.src = url
      img.onload = (e: Event) => {
        if (imgState.currentUrl !== url) {
          const ele: any[] = e.composedPath()
          if (props.rememberState) {
            // 保存当前图片的缩放信息
            stateMap.set(imgState.currentUrl, {
              scale: imgState.imgScale,
              top: imgState.imgTop,
              left: imgState.imgLeft,
              rotate: imgState.imgRotate,
            })
            // 如果之前已存储缩放信息，就应用
            const stateInfo = stateMap.get(url)
            if (stateInfo) {
              imgState.imgScale = stateInfo.scale
              imgState.imgTop = stateInfo.top
              imgState.imgRotate = stateInfo.rotate
              imgState.imgLeft = stateInfo.left
            }
            else {
              initState()
              if (props.defaultWidth)
                imgState.imgScale = props.defaultWidth / ele[0].naturalWidth
            }
          }
          else {
            if (props.defaultWidth)
              imgState.imgScale = props.defaultWidth / ele[0].naturalWidth
          }

          ele
            && emit('imgLoad', {
              index: imgState.currentIndex,
              dom: ele[0] as HTMLImageElement,
              url,
            })
        }
        imgState.currentUrl = url
        imgState.status = StatueEnum.DONE
      }
      img.onerror = (e: Event) => {
        const ele: EventTarget[] = e.composedPath()
        ele
          && emit('imgError', {
            index: imgState.currentIndex,
            dom: ele[0] as HTMLImageElement,
            url,
          })
        imgState.status = StatueEnum.FAIL
      }
    }

    // 关闭
    function handleClose(e: MouseEvent) {
      e && e.stopPropagation()
      close()
    }

    function close() {
      imgState.show = false
      // 移除火狐浏览器下的鼠标滚动事件
      document.body.removeEventListener('DOMMouseScroll', scrollFunc)
      // 恢复火狐及Safari浏览器下的图片拖拽
      document.ondragstart = null
    }

    // 图片复原
    function resume() {
      initState()
    }

    expose({
      resume,
      close,
      prev: handleChange.bind(null, 'left'),
      next: handleChange.bind(null, 'right'),
      setScale: (scale: number) => {
        if (scale > 0 && scale <= 10)
          imgState.imgScale = scale
      },
      setRotate: (rotate: number) => {
        imgState.imgRotate = rotate
      },
    })

    // 上一页下一页
    function handleChange(direction: 'left' | 'right') {
      const { currentIndex } = imgState
      const { imageList } = props
      if (direction === 'left') {
        imgState.currentIndex--
        if (currentIndex <= 0)
          imgState.currentIndex = imageList.length - 1
      }
      if (direction === 'right') {
        imgState.currentIndex++
        if (currentIndex >= imageList.length - 1)
          imgState.currentIndex = 0
      }
      handleIChangeImage(imageList[imgState.currentIndex])
    }

    function handleAddMoveListener(e: MouseEvent) {
      e = e || window.event
      imgState.moveX = e.clientX
      imgState.moveY = e.clientY
      const imgEl = unref(imgElRef)
      if (imgEl)
        imgEl.onmousemove = moveFunc
    }

    function moveFunc(e: MouseEvent) {
      e = e || window.event
      e.preventDefault()
      const movementX = e.clientX - imgState.moveX
      const movementY = e.clientY - imgState.moveY
      imgState.imgLeft += movementX
      imgState.imgTop += movementY
      imgState.moveX = e.clientX
      imgState.moveY = e.clientY
    }

    // 获取图片样式
    const getImageStyle = computed(() => {
      const { imgScale, imgRotate, imgTop, imgLeft } = imgState
      return {
        transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
        marginTop: `${imgTop}px`,
        marginLeft: `${imgLeft}px`,
        maxWidth: props.defaultWidth ? 'unset' : '100%',
      }
    })

    const getIsMultipleImage = computed(() => {
      const { imageList } = props
      return imageList.length > 1
    })

    watchEffect(() => {
      if (props.show)
        init()

      if (props.imageList)
        initState()
    })

    const handleMaskClick = (e: MouseEvent) => {
      if (props.maskClosable && e.target && (e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`))
        handleClose(e)
    }

    const renderClose = () => {
      return (
        <div class={`${prefixCls}__close`} onClick={handleClose}>
          <CloseOutlined class={`${prefixCls}__close-icon`} />
        </div>
      )
    }

    const renderIndex = () => {
      if (!unref(getIsMultipleImage))
        return null

      const { currentIndex } = imgState
      const { imageList } = props
      return (
        <div class={`${prefixCls}__index`}>
          {currentIndex + 1}
          {' '}
          /
          {imageList.length}
        </div>
      )
    }

    const renderController = () => {
      return (
        <div class={`${prefixCls}__controller`}>
          <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-getScaleStep.value)}>
            <img src={unScaleSvg} />
          </div>
          <div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(getScaleStep.value)}>
            <img src={scaleSvg} />
          </div>
          <div class={`${prefixCls}__controller-item`} onClick={resume}>
            <img src={resumeSvg} />
          </div>
          <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
            <img src={unRotateSvg} />
          </div>
          <div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
            <img src={rotateSvg} />
          </div>
        </div>
      )
    }

    const renderArrow = (direction: 'left' | 'right') => {
      if (!unref(getIsMultipleImage))
        return null

      return (
        <div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
          {direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
        </div>
      )
    }

    return () => {
      return (
        imgState.show && (
          <div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp} onClick={handleMaskClick}>
            <div class={`${prefixCls}-content`}>
              {/* <Spin */}
              {/*  indicator={<LoadingOutlined style="font-size: 24px" spin />} */}
              {/*  spinning={true} */}
              {/*  class={[ */}
              {/*    `${prefixCls}-image`, */}
              {/*    { */}
              {/*      hidden: imgState.status !== StatueEnum.LOADING, */}
              {/*    }, */}
              {/*  ]} */}
              {/* /> */}
              <img
                style={unref(getImageStyle)}
                class={[`${prefixCls}-image`, imgState.status === StatueEnum.DONE ? '' : 'hidden']}
                ref={imgElRef}
                src={imgState.currentUrl}
                onMousedown={handleAddMoveListener}
              />
              {renderClose()}
              {renderIndex()}
              {renderController()}
              {renderArrow('left')}
              {renderArrow('right')}
            </div>
          </div>
        )
      )
    }
  },
})
</script>

<style lang="less">
.img-preview {
  position: fixed;
  inset: 0;
  z-index: @preview-comp-z-index;
  user-select: none;
  background: rgb(0 0 0 / 50%);

  &-content {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: @white;
  }

  &-image {
    cursor: pointer;
    transition: transform 0.3s;
  }

  &__close {
    position: absolute;
    top: -40px;
    right: -40px;
    width: 80px;
    height: 80px;
    overflow: hidden;
    color: @white;
    cursor: pointer;
    background-color: rgb(0 0 0 / 50%);
    border-radius: 50%;
    transition: all 0.2s;

    &-icon {
      position: absolute;
      top: 46px;
      left: 16px;
      font-size: 16px;
    }

    &:hover {
      background-color: rgb(0 0 0 / 80%);
    }
  }

  &__index {
    position: absolute;
    bottom: 5%;
    left: 50%;
    padding: 0 22px;
    font-size: 16px;
    background: rgb(109 109 109 / 60%);
    border-radius: 15px;
    transform: translateX(-50%);
  }

  &__controller {
    position: absolute;
    bottom: 10%;
    left: 50%;
    display: flex;
    justify-content: center;
    width: 260px;
    height: 44px;
    padding: 0 22px;
    margin-left: -139px;
    background: rgb(109 109 109 / 60%);
    border-radius: 22px;

    &-item {
      display: flex;
      height: 100%;
      padding: 0 9px;
      font-size: 24px;
      cursor: pointer;
      transition: all 0.2s;

      &:hover {
        transform: scale(1.2);
      }

      img {
        width: 1em;
      }
    }
  }

  &__arrow {
    position: absolute;
    top: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 50px;
    height: 50px;
    font-size: 28px;
    cursor: pointer;
    background-color: rgb(0 0 0 / 50%);
    border-radius: 50%;
    transition: all 0.2s;

    &:hover {
      background-color: rgb(0 0 0 / 80%);
    }

    &.left {
      left: 50px;
    }

    &.right {
      right: 50px;
    }
  }
}
</style>
